using System;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Web;

namespace Pegasus.Web.Security.SecureWebPage
{
	/// <summary>
	/// The different modes supported for the &lt;secureWebPages&gt; configuration section.
	/// </summary>
	public enum SecureWebPageMode {
		/// <summary>
		/// Indicates that web page security is on and all requests should be monitored.
		/// </summary>
		On,

		/// <summary>
		/// Only remote requests are to be monitored.
		/// </summary>
		RemoteOnly,

		/// <summary>
		/// Only local requests are to be monitored.
		/// </summary>
		LocalOnly,

		/// <summary>
		/// Web page security is off and no monitoring should occur.
		/// </summary>
		Off
	}

	/// <summary>
	/// The different modes for bypassing security warnings.
	/// </summary>
	public enum SecurityWarningBypassMode {
		/// <summary>
		/// Always bypass security warnings when switching to an unencrypted page.
		/// </summary>
		AlwaysBypass,

		/// <summary>
		/// Only bypass security warnings when switching to an unencrypted page if the proper query parameter is present.
		/// </summary>
		BypassWithQueryParam,

		/// <summary>
		/// Never bypass security warnings when switching to an unencrypted page.
		/// </summary>
		NeverBypass
	}


	/// <summary>
	/// Contains the settings of a secureWebPages configuration section.
	/// </summary>
	public class SecureWebPageSettings : ConfigurationSection {

        private const string UnsecureProtocolPrefix = "http://";
        private const string SecureProtocolPrefix = "https://";

        /// <summary>
		/// Creates an instance of SecureWebPageSettings.
		/// </summary>
		public SecureWebPageSettings()
			: base() {
		}

		#region Properties

		/// <summary>
		/// Gets or sets the name of the query parameter that will indicate to the module to bypass
		/// any security warning if WarningBypassMode = BypassWithQueryParam.
		/// </summary>
		[ConfigurationProperty("bypassQueryParamName")]
		public string BypassQueryParamName {
			get { return (string)this["bypassQueryParamName"]; }
			set { this["bypassQueryParamName"] = value; }
		}

		/// <summary>
		/// Gets or sets the path to a URI for encrypted redirections, if any.
		/// </summary>
		[ConfigurationProperty("encryptedUri"), RegexStringValidator(@"^(?:|(?:https://)?[\w\-][\w\.\-]*(?:/[\w\.\-]+)*/?)$")]
		public string EncryptedUri {
			get { return (string)this["encryptedUri"]; }
			set {
				if (value != null && value.Length > 0)
					this["encryptedUri"] = value;
				else
					this["encryptedUri"] = null;
			}
		}

		/// <summary>
		/// Gets or sets a flag indicating whether or not to maintain the current path when redirecting
		/// to a different host.
		/// </summary>
		[ConfigurationProperty("maintainPath", DefaultValue = true)]
		public bool MaintainPath {
			get { return (bool)this["maintainPath"]; }
			set { this["maintainPath"] = value; }
		}

		/// <summary>
		/// Gets or sets the mode indicating how the secure web page settings handled.
		/// </summary>
		[ConfigurationProperty("mode", DefaultValue = SecureWebPageMode.On)]
		public SecureWebPageMode Mode {
			get { return (SecureWebPageMode)this["mode"]; }
			set { this["mode"] = value; }
		}

		/// <summary>
		/// Gets the collection of directory settings read from the configuration section.
		/// </summary>
		[ConfigurationProperty("directories")]
		public SecureWebPageDirectorySettingCollection Directories {
			get { return (SecureWebPageDirectorySettingCollection)this["directories"]; }
		}

		/// <summary>
		/// Gets the collection of file settings read from the configuration section.
		/// </summary>
		[ConfigurationProperty("files")]
		public SecureWebPageFileSettingCollection Files {
			get { return (SecureWebPageFileSettingCollection)this["files"]; }
		}

        /// <summary>
        /// Gets the collection of file settings read from the configuration section.
        /// </summary>
        [ConfigurationProperty("domains")]
        public SecureWebPageDomainSettingCollection Domains
        {
            get { return (SecureWebPageDomainSettingCollection)this["domains"]; }
        }

        /// <summary>
		/// Gets or sets the path to a URI for unencrypted redirections, if any.
		/// </summary>
		[ConfigurationProperty("unencryptedUri"), RegexStringValidator(@"^(?:|(?:http://)?[\w\-][\w\.\-]*(?:/[\w\.\-]+)*/?)$")]
		public string UnencryptedUri {
			get { return (string)this["unencryptedUri"]; }
			set {
				if (value != null && value.Length > 0)
					this["unencryptedUri"] = value;
				else
					this["unencryptedUri"] = null;
			}
		}

		/// <summary>
		/// Gets or sets the bypass mode indicating whether or not to bypass security warnings
		/// when switching to a unencrypted page.
		/// </summary>
		[ConfigurationProperty("warningBypassMode", DefaultValue = SecurityWarningBypassMode.BypassWithQueryParam)]
		public SecurityWarningBypassMode WarningBypassMode {
			get { return (SecurityWarningBypassMode)this["warningBypassMode"]; }
			set { this["warningBypassMode"] = value; }
		}

		#endregion

        /// <summary>
        /// Gets the domain settings for the current web domain.
        /// </summary>
        /// <returns></returns>
        public SecureWebPageDomainSetting GetCurrentDomainMapping()
        {
            // If we are secure, then key off of the encrypted domain.
            bool keyOffOfEncrypted = HttpContext.Current.Request.Url.AbsoluteUri.StartsWith(SecureProtocolPrefix);

            // get a blank domain setting.
            SecureWebPageDomainSetting domain = new SecureWebPageDomainSetting(null, null);

            // If we have any settings at the top level, then let's just see if those are the ones we need.
            Uri uri = ConvertToUri(keyOffOfEncrypted ? EncryptedUri : UnencryptedUri);
            if (uri != null)
            {
                // If there's a match, then just use this setting.
                if (HttpContext.Current.Request.Url.Authority == uri.Authority)
                {
                    domain.UnencryptedUri = UnencryptedUri;
                    domain.EncryptedUri = EncryptedUri;
                    return domain;
                }
            }

            // Now go through all of the settings in the "domains" tag group. If there is a match, then use it.
            foreach (SecureWebPageDomainSetting dsetting in Domains)
            {
                uri = ConvertToUri(keyOffOfEncrypted ? dsetting.EncryptedUri : dsetting.UnencryptedUri);
                if (uri != null)
                {
                    if (HttpContext.Current.Request.Url.Authority == uri.Authority)
                    {
                        return dsetting;
                    }
                }
            }

            // Return the domain settings.
            return domain;
        }

        /// <summary>
        /// Converts the given uri into a Uri object.
        /// </summary>
        /// <param name="uri">uri string</param>
        /// <returns>uri object</returns>
        private Uri ConvertToUri(
            string uri)
        {
            if (String.IsNullOrEmpty(uri)) return null;

            // Ensure there is a protocol or a Uri cannot be constructed.
            if (!uri.StartsWith(UnsecureProtocolPrefix) && !uri.StartsWith(SecureProtocolPrefix))
            {
                uri = UnsecureProtocolPrefix + uri;
            }

            // Extract the authority and path to build a string suitable for our needs.
            return new Uri(uri);
        }

    }

}
